#include "Model3DInstance.h"
#include "ModelPostureBone.h"
#include "Game.h"
#include "Camera.h"
#include "Model3D.h"
#include "Mesh.h"
#include "Bone.h"
#include "ModelsManager.h"
#include "ShadowMap.h"

Model3DInstance::Model3DInstance()
{
	mRenderDistanceMin = 0;
	mRenderDistanceMax = 10000;
	mMainMesh = NULL;
}

Model3DInstance::Model3DInstance(const Model3DInstance&)
{

}


Model3DInstance::~Model3DInstance()
{

}

Model3DInstance Model3DInstance::operator=(Model3DInstance const&)
{
	return Model3DInstance();
}

bool Model3DInstance::Initialize(Model3D* pModel3d, XMFLOAT3 &pPosition, XMFLOAT4 &pRotation)
{
	mGame = Game::Instance();
	mIsRendering = false;
	mPosition = pPosition;
	mAmount = 0;

	XMMATRIX matrixPosition = XMMatrixTranslationFromVector( XMLoadFloat3(&pPosition) );
	XMMATRIX matrixRotation = XMMatrixRotationQuaternion( XMLoadFloat4(&pRotation) );
	XMStoreFloat4x4(&mTransformation, matrixRotation * matrixPosition);

	mModel3d = pModel3d;

	std::vector<Mesh*>::iterator GetMeshBegin();
	std::vector<Mesh*>::iterator GetMeshEnd();

	mMeshInstanceCount = pModel3d->GetMeshesCount();
	mMeshInstances = new MeshInstance[pModel3d->GetMeshesCount()];
	int meshesCount = pModel3d->GetMeshesCount();
	for(int index = 0;index < meshesCount;++index)
	{
		Mesh* mesh = pModel3d->GetMeshByIndex(index);
		Bone* bone = mesh->GetBone();

		RenderItem* renderItem = new RenderItem();
		renderItem->SetMeshIndex(index);

		XMVECTOR v = XMLoadFloat3(&mesh->GetCenter()) + XMLoadFloat3(&pPosition);
		XMFLOAT3 position;
		XMStoreFloat3(&position, v);

		renderItem->SetPosition(position);
		renderItem->SetModel3DInstance(this);
		if(mesh->GetName().find("_decal") != std::string::npos)
		{
			renderItem->SetRenderItemManager(mGame->GetDecalRenderItemManager());
		}
		else if(mesh->IsTextureTransparent())
		{
			renderItem->SetRenderItemManager(mGame->GetTransparentRenderItemManager());
		}
		else
		{
			renderItem->SetRenderItemManager(mGame->GetMainRenderItemManager());
		}

		mMeshInstances[index].mMesh = mesh;
		mMeshInstances[index].mRenderItem = renderItem;
		mMeshInstances[index].mParentMeshInstance = NULL;
		mMeshInstances[index].mCurrentPosture = NULL;
		mMeshInstances[index].mNextPosture = NULL;

		if(bone != NULL)
		{
			if(bone->IsMain())
			{
				mMainMesh = &mMeshInstances[index];
			}
			if(bone->GetParent() != NULL)
			{
				mMeshInstances[index].mParentMeshInstance = &mMeshInstances[bone->GetParent()->GetMeshIndex()];
			}
		}

		XMStoreFloat4x4(&mMeshInstances[index].mTransform, XMMatrixIdentity());
	}
	for(int index = 0;index < mMeshInstanceCount;index++)
	{
		Bone* bone = mMeshInstances[index].mMesh->GetBone();
		if(bone == NULL) continue;
		Bone* parentBone = bone->GetParent();
		if(parentBone == NULL) continue;
		int parentIndex = parentBone->GetMeshIndex();
		mMeshInstances[index].mParentMeshInstance = &mMeshInstances[parentIndex];
	}
	
	return true;
}

void Model3DInstance::Uninit()
{

}

void Model3DInstance::SetRenderDistance(float pRenderDistanceMin, float pRenderDistanceMax)
{
	mRenderDistanceMin = pRenderDistanceMin;
	mRenderDistanceMax = pRenderDistanceMax;
}

void Model3DInstance::PreRender(float pDistance)
{
	mAmount += 0.01f;
	if(mAmount > 1) mAmount = 0; 
	if(mRenderDistanceMin < pDistance && pDistance < mRenderDistanceMax)
	{
		mIsRendering = true;
		for(int index = 0;index < mMeshInstanceCount;index++)
		{
			mMeshInstances[index].mRenderItem->StartRendering();
			mMeshInstances[index].mRenderItem->CalculDistance(mGame->GetOrigine());
			CalculLocalTransform(index);
		}
		if(mMainMesh != NULL)
		{
			CalculFinalTransform(mMainMesh);
		}
		else
		{
			for(int index = 0;index < mMeshInstanceCount;index++)
			{
				CalculFinalTransform(&mMeshInstances[index]);
			}
		}
	}
	else
	{
		StopRender();
	}
}


void Model3DInstance::CalculLocalTransform(int pMeshIndex)
{
	Bone* bone = mModel3d->GetBoneByMeshIndex(pMeshIndex);
	if(bone != NULL)
	{
		XMFLOAT3* origine = mMeshInstances[pMeshIndex].mMesh->GetOrigine();

		XMMATRIX t1;
		XMMATRIX t2;

		t1 = XMMatrixTranslation(origine->x * -1, origine->y * -1, origine->z * -1 );
		t2 = XMMatrixTranslation(origine->x, origine->y, origine->z );

		if(mMeshInstances[pMeshIndex].mCurrentPosture != NULL && mMeshInstances[pMeshIndex].mNextPosture != NULL)
		{
			XMMATRIX tranform = XMLoadFloat4x4(&GetModelPostureBoneLerp(mMeshInstances[pMeshIndex].mCurrentPosture, mMeshInstances[pMeshIndex].mNextPosture, mAmount));
			XMStoreFloat4x4(&mMeshInstances[pMeshIndex].mLocalTransform, t1 * tranform * t2);
		}
		else if(mMeshInstances[pMeshIndex].mCurrentPosture != NULL)
		{
			XMMATRIX tranform = XMLoadFloat4x4(&mMeshInstances[pMeshIndex].mCurrentPosture->GetTransform());
			XMStoreFloat4x4(&mMeshInstances[pMeshIndex].mLocalTransform, t1 * tranform * t2);
		}
		else
		{
			XMStoreFloat4x4(&mMeshInstances[pMeshIndex].mLocalTransform, t1 * XMLoadFloat4x4(&mMeshInstances[pMeshIndex].mTransform)  * t2);
		}
	}
	else
	{
		mMeshInstances[pMeshIndex].mLocalTransform = mMeshInstances[pMeshIndex].mTransform;
	}
}

void Model3DInstance::CalculFinalTransform(MeshInstance* pMesh)
{
	if(pMesh->mParentMeshInstance != NULL)
	{
		XMStoreFloat4x4(&pMesh->mFinalTransform, XMLoadFloat4x4(&pMesh->mLocalTransform) * XMLoadFloat4x4(&pMesh->mParentMeshInstance->mFinalTransform));
	}
	else
	{
		pMesh->mFinalTransform = pMesh->mLocalTransform;
	}

	Bone* bone = pMesh->mMesh->GetBone();
	if(bone != NULL)
	{
		for(std::vector<Bone*>::iterator itBone = bone->GetSubBonesBegin(); itBone != bone->GetSubBonesEnd();++itBone)
		{
			MeshInstance* subMeshInstance = &mMeshInstances[(*itBone)->GetMeshIndex()];
			CalculFinalTransform(subMeshInstance);
		}
	}
}

void Model3DInstance::StopRender()
{
	if(mIsRendering)
	{
		mIsRendering = false;
		for(int index = 0;index < mMeshInstanceCount;index++)
		{
			mMeshInstances[index].mRenderItem->StopRendering();
		}
	}
}

void Model3DInstance::RenderMesh(int pMeshIndex)
{
	XMFLOAT4X4* viewMatrix;
	XMFLOAT4X4* projectionMatrix;
	XMFLOAT4X4 worldMatrix;

	viewMatrix = Game::Instance()->GetCamera()->GetViewMatrix();
	projectionMatrix = CDirect3D::Instance()->GetProjectionMatrix();

	XMStoreFloat4x4(&worldMatrix, XMLoadFloat4x4(&mMeshInstances[pMeshIndex].mFinalTransform) * XMLoadFloat4x4(&mTransformation));

	Mesh* mesh = mMeshInstances[pMeshIndex].mMesh;
	mesh->Render(worldMatrix, *viewMatrix, *projectionMatrix);
}

XMFLOAT4X4 Model3DInstance::GetTransform()
{
	return mTransformation;
}

void Model3DInstance::Render()
{
	XMFLOAT4X4* viewMatrix;
	XMFLOAT4X4* projectionMatrix;

	viewMatrix = Game::Instance()->GetCamera()->GetViewMatrix();
	projectionMatrix = CDirect3D::Instance()->GetProjectionMatrix();

	mModel3d->Render(mTransformation, *viewMatrix, *projectionMatrix);
}

//void Model3DInstance::ShadowRender(ShadowMap* pShadowMap)
//{
//	mModel3d->ShadowRender(pShadowMap, mTransformation);
//}

void Model3DInstance::setTransform(const XMFLOAT4X4 &pTranform)
{
	mTransformation = pTranform;
}

Model3D* Model3DInstance::GetModel3D()
{
	return mModel3d;
}

void Model3DInstance::GetBoundingBox(BoundingBox &pBoundingBox)
{
	bool first = true;
	BoundingBox tmpBoundingBox;
	int meshesCount = mModel3d->GetMeshesCount();
	for(int index = 0; index < meshesCount;++index)
	{
		if( first )
		{
			mModel3d->GetMeshByIndex(index)->GetBoundingBox(pBoundingBox, mTransformation);
			first = false;
		}
		else
		{
			mModel3d->GetMeshByIndex(index)->GetBoundingBox(tmpBoundingBox, mTransformation);
			AddBoundingBox(pBoundingBox, tmpBoundingBox);
		}
	}
}

MeshInstance* Model3DInstance::GetMeshInstance(int pIndex)
{
	return &mMeshInstances[pIndex];
}

void Model3DInstance::ApplyTransformToMesh(int pMeshIndex, const XMFLOAT4X4 &pTransform)
{
	mMeshInstances[pMeshIndex].mTransform = pTransform;
}

void Model3DInstance::SetPosture(std::string pPostureName)
{
	ModelPostureLink* link = ModelsManager::Instance()->GetModelPostureLink(pPostureName, mModel3d);
	for(int index = 0; index < mMeshInstanceCount;++index)
	{
		mMeshInstances[index].mCurrentPosture = link->GetModelPostureBoneByMeshIndex(index);
	}
}

void Model3DInstance::SetNextPosture(std::string pPostureName)
{
	ModelPostureLink* link = ModelsManager::Instance()->GetModelPostureLink(pPostureName, mModel3d);
	for(int index = 0; index < mMeshInstanceCount;++index)
	{
		mMeshInstances[index].mNextPosture = link->GetModelPostureBoneByMeshIndex(index);
	}
}

void Model3DInstance::SetPostureAmount(float pAmount)
{
	mAmount = pAmount;
}